﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD {




    /// <summary>
    /// キーフレームシーケンスの型に依存しない実装
    /// </summary>
    /// <remarks>
    /// キーフレームシーケンスは複数のキーフレームを格納するコンテナクラスです。
    /// 1つのプロパティの時間変化(アニメーション)を表します。
    /// 一連のキーフレームの時刻は必ず単調増加である必要があります。
    /// 定義された補完方法を元にサンプリングを行い時刻ｔの値を計算します。
    /// サンプリングの結果はキーフレームの値型にかかわらずfloatで受け取ります。
    /// </remarks>
    public abstract class KeyframeSequence : Object {

        #region Field
        Type valueType;
        InterpolationType interpolationType;
        KeyframeRepeatMode repeatMode;
        int keyframeCount;
        int componentCount;
        int duration;
        int validRangeStart;
        int validRangeEnd;
        bool shouldBeNormalized;

        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// </remarks>
        /// <param name="numKeyframes">キーフレーム数</param>
        /// <param name="numComponents">コンポーネント数</param>
        /// <param name="valueType">値の型</param>
        public KeyframeSequence (int numKeyframes, int numComponents, Type valueType) {
            this.valueType = valueType;
            this.interpolationType = DDD.InterpolationType.Linear;
            this.repeatMode = DDD.KeyframeRepeatMode.Once;
            this.keyframeCount = numKeyframes;
            this.componentCount = numComponents;
            this.duration = 0;
            this.validRangeStart = 0;
            this.validRangeEnd = 0;
            this.shouldBeNormalized = false;
        }
        #endregion

        #region Property

        /// <summary>
        /// キーフレームの持つValueの型
        /// </summary>
        /// <remarks>
        /// キーフレームは任意の型を保持できます。ただしfloatに暗黙的にキャストできる必要があります。
        /// </remarks>
        public Type ValueType {
            get { return valueType; }
        }


        /// <summary>
        /// コンポーネント数
        /// </summary>
        public int ComponentCount {
            get { return componentCount; }
        }

        /// <summary>
        /// アニメーションの長さ（ローカル時間)
        /// </summary>
        /// <remarks>
        /// <see cref="Duration"/>はこのキーフレームシーケンスに設定された
        /// アニメーションの長さです
        /// </remarks>
        public int Duration {
            get { return duration; }
        }

        /// <summary>
        /// 補完方法
        /// </summary>
        /// <remarks>
        /// キーフレーム間の補完方法です。
        /// </remarks>
        public InterpolationType InterpolationType {
            get { return interpolationType; }
        }

        /// <summary>
        /// 繰り返しモード
        /// </summary>
        /// <remarks>
        /// 一連のシーケンスを再生した後の2週目をどうするかの設定です。
        /// </remarks>
        public KeyframeRepeatMode RepeatMode {
            get { return repeatMode; }
        }

        /// <summary>
        /// 保持しているキーフレームの個数
        /// </summary>
        public int KeyframeCount {
            get { return keyframeCount; }
        }

        /// <summary>
        /// 最初の有効キーフレームの番号
        /// </summary>
        /// <remarks>
        /// 一連のキーフレームのうち<see cref="ValidRangeStart"/>～<see cref="ValidRangeEnd"/>のものが有効です。
        /// (startとendは両方含む)
        /// </remarks>
        public int ValidRangeStart {
            get { return validRangeStart; }
        }

        /// <summary>
        /// 最後の有効キーフレーム番号
        /// </summary>
        /// <remarks>
        /// 一連のキーフレームのうち<see cref="ValidRangeStart"/>～<see cref="ValidRangeEnd"/>のものが有効です。
        /// (startとendは両方含む)
        /// </remarks>
        public int ValidRangeEnd {
            get { return validRangeEnd; }
        }

        /// <summary>
        /// 正規化すべきかどうかのフラグ
        /// </summary>
        /// <remarks>
        /// このフラグがtrueの時はキーフレームの値は(格納されている値ではなく)それを正規化した値が使用されます。
        /// unsigned型の場合[0,1]に、singed型の場合[-1,1]にマップされます。
        /// </remarks>
        public bool ShouldBeNormalized {
            get { return shouldBeNormalized; }
        }

        /// <summary>
        /// このオブジェクトが参照する全DDDオブジェクトを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 参照にnullは含まれません。同一のオブジェクトが複数出現する可能性があります。
        /// </remarks>
        public override IEnumerable<Object> References {
            get {
                return base.References;
            }
        }
        #endregion



        #region Method

        /// <summary>
        /// サンプリングして結果をfloatの配列で取得します
        /// </summary>
        /// <param name="time">ローカル時間</param>
        /// <param name="value">サンプリング結果を受け取る配列</param>
        /// <remarks>直接戻り値で配列を受け取らないのは頻繁なnew/deleteを抑制するため</remarks>
        public abstract void Sample (float time, float[] value);

        /// <summary>
        /// アニメーションの長さを設定します
        /// </summary>
        /// <remarks>
        /// アニメーションの長さは最後のキーフレームの時刻より以上である必要があります。
        /// （そうでないと繰り返し時に不都合が出ます）
        /// ※ Durationの適切な日本語訳を探し中
        /// </remarks>
        /// <param name="duration">アニメーションの長さ(ローカル時間)</param>
        public void SetDuration (int duration) {
            if (duration < 0) {
                throw new ArgumentException ("Duration is invalid");
            }
            this.duration = duration;
        }

        /// <summary>
        /// シーケンスの有効キーフレームを設定します
        /// </summary>
        /// <remarks>
        /// 一連のキーフレームは一部だけを指定して有効化することができます。
        /// これは複数のアニメーションを1つのキーフレームにまとめて収納する時に使用します。
        /// １つのモデルに複数のアニメーションをつけて出力できないツールのための機能です。
        /// start=0,end=0を指定すると全フレームが有効化されます。
        /// </remarks>
        /// <param name="start">有効キーフレームの開始インデックス(このフレームも含む)</param>
        /// <param name="end">有効キーフレームの終了インデックス(このフレームも含む)</param>
        public void SetValidRange (int start, int end) {
            if (start > end) {
                throw new ArgumentException ("be start <= end");
            }
            this.validRangeStart = start;
            this.validRangeEnd = end;
        }

        /// <summary>
        /// 繰り返しモードを設定します
        /// </summary>
        /// <remarks>
        /// 繰り返しモードを<see cref="KeyframeRepeatMode.Once"/>,<see cref="KeyframeRepeatMode.Loop"/>,
        /// <see cref="KeyframeRepeatMode.AdditiveLoop"/>の中から選択します。
        /// 初期値は<see cref="KeyframeRepeatMode.Once"/>です。
        /// </remarks>
        /// <param name="repeatMode">繰り返しモード</param>
        public void SetRepeatMode (KeyframeRepeatMode repeatMode) {
            this.repeatMode = repeatMode;
        }

        /// <summary>
        /// 補間モード
        /// </summary>
        /// <remarks>
        /// <see cref="PixelFormat.Alpha"/>
        /// 補間モードを<dic>Step</dic>,<dic>Linear</dic>,<dic>Spline</dic>,<dic>Slerp</dic>,<dic>SQuad</dic>の中から選択します。
        /// 初期値は<dic>Linear</dic>です。
        /// </remarks>
        /// <param name="interpType">補完方法</param>
        public void SetInterpolationMode (InterpolationType interpType) {
            this.interpolationType = interpType;
        }

        /// <summary>
        /// 2つのキーフレームシーケンスが類似型かどうかを判定します
        /// </summary>
        /// <remarks>
        /// 同じ型で同じキーフレーム数、コンポーネント数の時「類似している」と見なされます。
        /// 中身のデータに関してはノータッチです。
        /// </remarks>
        /// <param name="rhs">比較対象のキーフレームシーケンス</param>
        /// <returns></returns>
        public bool Similar (KeyframeSequence rhs) {
            return (keyframeCount == rhs.keyframeCount &&
                    componentCount == rhs.componentCount &&
                    valueType == rhs.valueType) ? true : false;
        }

        #endregion

    }


    /// <summary>
    /// キーフレームシーケンスの型依存実装
    /// </summary>
    /// <remarks>
    /// キーフレームシーケンスの型依存部分の実装です。
    /// 実装の都合で2つに分かれています。
    /// </remarks>
    /// <typeparam name="TValue">Valueの型</typeparam>
    public class KeyframeSequence<TValue> : KeyframeSequence, IEnumerable<Keyframe<TValue>> where TValue : struct {

        #region Field
        Keyframe<TValue>[] keyframes;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// 指定の個数のキーフレームを保持できる<see cref="KeyframeSequence"/>オブジェクトを作成します。
        /// コンポーネント数は1～4です。
        /// </remarks>
        /// <param name="numComponents">コンポーネント数</param>
        /// <param name="numKeyframes">キーフレーム数</param>
        public KeyframeSequence (int numKeyframes, int numComponents) :
            base (numKeyframes, numComponents, typeof (TValue)) {
            if (numKeyframes <= 0 || numKeyframes > Int32.MaxValue - 4) {
                throw new ArgumentException ("Number of keyrames is invalid");
            }
            if (numComponents <= 0 || numComponents > 4) {
                throw new ArgumentException ("Number of Components is invalid");
            }
            if (!(typeof (TValue) != typeof (int) || typeof (TValue) != typeof (uint) ||
                typeof (TValue) != typeof (short) || typeof (TValue) != typeof (ushort) ||
                typeof (TValue) != typeof (byte) || typeof (TValue) != typeof (float))) {
                throw new ArgumentException ("TValue is invalid");
            }

            this.keyframes = new Keyframe<TValue>[4 + numKeyframes];
        }
        #endregion

        #region Property

        #endregion

        #region Method


        /// <summary>
        /// 指定のインデックスのキーフレームを取得します
        /// </summary>
        /// <param name="index">キーフレーム番号</param>
        /// <returns>キーフレーム</returns>
        public Keyframe<TValue> GetKeyframe (int index) {
            if (index < 0 || index >= KeyframeCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            return keyframes[2 + index];
        }


        int SlideTime (ref float time) {
            var loopCount = 0;

            while (time < 0 || time >= Duration) {
                if (time < 0) {
                    time += Duration;
                    loopCount -= 1;
                }
                if (time >= Duration) {
                    time -= Duration;
                    loopCount += 1;
                }
            }
            return loopCount;
        }

        /// <summary>
        /// サンプリングして結果をfloatの配列で取得します
        /// </summary>
        /// <remarks>
        /// このメソッドはキーフレームのValueが配列型のとき専用です。
        /// ※ サンプリング結果の配列を直接戻り値で受け取らないのは頻繁なnew/finalizeを避けるためです。
        /// </remarks>
        /// <param name="time">時刻ｔ(ローカル時間)</param>
        /// <param name="value">サンプリング結果を受け取る配列</param>
        public override void Sample (float time, float[] value) {
            if (value == null) {
                throw new ArgumentNullException ("Value is null");
            }
            if (value.Length < ComponentCount) {
                throw new ArgumentException ("Value is too short");
            }

            var N = KeyframeCount;

            switch (RepeatMode) {
                case KeyframeRepeatMode.Once:
                    keyframes[0] = new Keyframe<TValue> (Int32.MinValue, keyframes[2].Value);
                    keyframes[1] = new Keyframe<TValue> (Int32.MinValue, keyframes[2].Value);
                    keyframes[N + 2] = new Keyframe<TValue> (Int32.MaxValue, keyframes[N + 1].Value);
                    keyframes[N + 3] = new Keyframe<TValue> (Int32.MaxValue, keyframes[N + 1].Value);
                    break;
                case KeyframeRepeatMode.Loop:
                    var loopCount = SlideTime (ref time);
                    keyframes[0] = new Keyframe<TValue> (keyframes[N].Time - Duration, keyframes[N].Value);
                    keyframes[1] = new Keyframe<TValue> (keyframes[N + 1].Time - Duration, keyframes[N + 1].Value);
                    keyframes[N + 2] = new Keyframe<TValue> (keyframes[2].Time + Duration, keyframes[2].Value);
                    keyframes[N + 3] = new Keyframe<TValue> (keyframes[3].Time + Duration, keyframes[3].Value);
                    break;
            }

            var left = Array.FindLastIndex (keyframes, x => x.Time <= time);
            var left2 = left - 1;
            var right = Array.FindIndex (keyframes, x => x.Time > time);
            var right2 = right + 1;

            var s = (time - keyframes[left].Time) / ((float)keyframes[right].Time - keyframes[left].Time);
            var k0 = keyframes[left2];
            var k1 = keyframes[left];
            var k2 = keyframes[right];
            var k3 = keyframes[right2];

            switch (InterpolationType) {
                case InterpolationType.Step: Step (s, k0, k1, k2, k3, value); break;
                case InterpolationType.Linear: Linear (s, k0, k1, k2, k3, value); break;
                case InterpolationType.Spline: Spline (s, k0, k1, k2, k3, value); break;
                case InterpolationType.Slerp: Slerp (s, k0, k1, k2, k3, value); break;
                case InterpolationType.SQuad: SQuad (s, k0, k1, k2, k3, value); break;
            }
        }

        void Step (float s, Keyframe<TValue> k0, Keyframe<TValue> k1, Keyframe<TValue> k2, Keyframe<TValue> k3, float[] value) {
            for (var i = 0; i < ComponentCount; i++) {
                value[i] = (float)(ValueType)k1.Value[i];
            }
        }

        void Linear (float s, Keyframe<TValue> k0, Keyframe<TValue> k1, Keyframe<TValue> k2, Keyframe<TValue> k3, float[] value) {
            for (var i = 0; i < ComponentCount; i++) {
                var f1 = (float)(ValueType)k1.Value[i];
                var f2 = (float)(ValueType)k2.Value[i];
                value[i] = (1 - s) * f1 + s * f2;
            }
        }

        /// <summary>
        /// Catmull-Romスプライン補間を行います
        /// </summary>
        /// <remarks>
        /// キーフレーム1,2の間の位置sの補間値をCatmull-Romスプラインに基づいて計算します。
        /// (*1) Catmull-Romスプラインのサンプリング間隔が一様でないときの補正項だが
        /// 最近省略してしまっても問題ないような気がしている。
        /// (*1) 定義より端点の時この項は0. この実装ではOnceの時にt=Int32.Min(又はMax)の点を両端に挿入しているので
        /// 限りなく0に近い値になる。
        /// </remarks>
        /// <param name="s">補間位置[0,1]</param>
        /// <param name="k0">キーフレーム0</param>
        /// <param name="k1">キーフレーム1</param>
        /// <param name="k2">キーフレーム2</param>
        /// <param name="k3">キーフレーム3</param>
        /// <param name="value">計算結果を書き込む配列</param>
        void Spline (float s, Keyframe<TValue> k0, Keyframe<TValue> k1, Keyframe<TValue> k2, Keyframe<TValue> k3, float[] value) {

            var a = 2 * s * s * s - 3 * s * s + 1;
            var b = -2 * s * s * s + 3 * s * s;
            var c = s * s * s - 2 * s * s + s;
            var d = s * s * s - s * s;

            var t0 = k0.Time;
            var t1 = k1.Time;
            var t2 = k2.Time;
            var t3 = k3.Time;

            for (var i = 0; i < ComponentCount; i++) {
                var v0 = (float)(ValueType)k0.Value[i];
                var v1 = (float)(ValueType)k1.Value[i];
                var v2 = (float)(ValueType)k2.Value[i];
                var v3 = (float)(ValueType)k3.Value[i];

                var t1l = 2 * (t2 - t1) / (t2 - t0) * (v2 - v0) / 2.0f;   // (*1)
                var t2r = 2 * (t2 - t1) / (t3 - t1) * (v3 - v1) / 2.0f;   // (*1)

                value[i] = a * v1 + b * v2 + c * t1l + d * t2r;
            }
        }

        void Slerp (float s, Keyframe<TValue> k0, Keyframe<TValue> k1, Keyframe<TValue> k2, Keyframe<TValue> k3, float[] value) {
            var q1 = Quaternion.Set ((float)(ValueType)k1.Value[0],
             (float)(ValueType)k1.Value[1],
             (float)(ValueType)k1.Value[2],
             (float)(ValueType)k1.Value[3]);
            var q2 = Quaternion.Set ((float)(ValueType)k2.Value[0],
                                     (float)(ValueType)k2.Value[1],
                                     (float)(ValueType)k2.Value[2],
                                     (float)(ValueType)k2.Value[3]);
            var q = Quaternion.Slerp (s, q1, q2);

            value[0] = q[0];
            value[1] = q[1];
            value[2] = q[2];
            value[3] = q[3];
        }

        /// <summary>
        /// クォータニオンのスプライン補間を行います
        /// </summary>
        /// <remarks>
        /// すみません、実装していません。
        /// </remarks>
        /// <param name="s"></param>
        /// <param name="k0"></param>
        /// <param name="k1"></param>
        /// <param name="k2"></param>
        /// <param name="k3"></param>
        /// <param name="value"></param>
        void SQuad (float s, Keyframe<TValue> k0, Keyframe<TValue> k1, Keyframe<TValue> k2, Keyframe<TValue> k3, float[] value) {
            throw new NotImplementedException ("Sorry");
        }


        /// <summary>
        /// 指定の番号のキーフレームをセットします
        /// </summary>
        /// <remarks>
        /// キーフレームシーケンスのi番目のキーフーレムに時刻と値をセットします。
        /// </remarks>
        /// <param name="index">キーフレーム番号</param>
        /// <param name="time">時刻(ローカル時間)</param>
        /// <param name="value">値</param>
        public void SetKeyframe (int index, int time, TValue value) {
            if (index < 0 || index >= KeyframeCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            this.keyframes[2 + index] = new Keyframe<TValue> (time, value);
        }

        /// <summary>
        /// 指定の番号のキーフレームをセットします
        /// </summary>
        /// <remarks>
        /// キーフレームシーケンスのi番目のキーフレームに時刻と値をセットします。
        /// このメソッドは<see cref="KeyframeSequence.ComponentCount"/>=2の時のみ使用可能です。
        /// </remarks>
        /// <param name="index">キーフレーム番号</param>
        /// <param name="time">時刻(ローカル時間)</param>
        /// <param name="value1">値1</param>
        /// <param name="value2">値2</param>
        public void SetKeyframe (int index, int time, TValue value1, TValue value2) {
            if (index < 0 || index >= KeyframeCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            this.keyframes[2 + index] = new Keyframe<TValue> (time, value1, value2);
        }

        /// <summary>
        /// 指定の番号のキーフレームをセットします
        /// </summary>
        /// <remarks>
        /// キーフレームシーケンスのi番目のキーフーレムに時刻と値をセットします。
        /// このメソッドは<see cref="KeyframeSequence.ComponentCount"/>=3の時のみ使用可能です。
        /// </remarks>
        /// <param name="index">キーフレーム番号</param>
        /// <param name="time">時刻(ローカル時間)</param>
        /// <param name="value1">値1</param>
        /// <param name="value2">値2</param>
        /// <param name="value3">値3</param>
        public void SetKeyframe (int index, int time, TValue value1, TValue value2, TValue value3) {
            if (index < 0 || index >= KeyframeCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            this.keyframes[2 + index] = new Keyframe<TValue> (time, value1, value2, value3);
        }

        /// <summary>
        /// キーフレームをセットします
        /// </summary>
        /// <remarks>
        /// キーフレームシーケンスのi番目のキーフレムに時刻と値をセットします。
        /// このメソッドは<see cref="KeyframeSequence.ComponentCount"/>=3の時のみ使用可能です。
        /// </remarks>
        /// <param name="index">キーフレーム番号</param>
        /// <param name="time">時刻(ローカル時間)</param>
        /// <param name="value1">値1</param>
        /// <param name="value2">値2</param>
        /// <param name="value3">値3</param>
        /// <param name="value4">値4</param>
        public void SetKeyframe (int index, int time, TValue value1, TValue value2, TValue value3, TValue value4) {
            if (index < 0 || index >= KeyframeCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            this.keyframes[2 + index] = new Keyframe<TValue> (time, value1, value2, value3, value4);
        }

        /// <summary>
        /// キーフレームを取得するインデクサー
        /// </summary>
        /// <remarks>
        /// 指定のインデックスのキーフレームを取得します。
        /// (メモ) 戻り値は(いつものように)構造体のコピーですが<see cref="Keyframe{TValue}"/>構造体は
        /// Valueメンバーを配列で持つので実は(いつもと異なり)参照が返っています。
        /// 不要なコピーを避けるためにあえてこのような実装になっています。
        /// ここら辺の処理は結構重いので・・・
        /// </remarks>
        /// <param name="index">キーフレーム番号</param>
        /// <returns>キーフレーム</returns>
        public Keyframe<TValue> this[int index] {
            get {
                if (index < 0 || index >= KeyframeCount) {
                    throw new IndexOutOfRangeException ("Index is out of range");
                }
                return keyframes[2 + index];
            }
        }

        /// <summary>
        /// キーフレームを全て列挙する列挙子
        /// </summary>
        /// <returns></returns>
        IEnumerator<Keyframe<TValue>> IEnumerable<Keyframe<TValue>>.GetEnumerator () {
            return ((IEnumerable<Keyframe<TValue>>)keyframes).Skip (2).Take (KeyframeCount).GetEnumerator ();
        }

        /// <summary>
        /// キーフレームを全て列挙する列挙子
        /// </summary>
        /// <returns></returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
            return keyframes.Skip (2).Take (KeyframeCount).GetEnumerator ();
        }

        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary> 
        public override string ToString () {
            return string.Format ("KeyframeSequence: Keyframes={0},{1}, Duration={2}, ValidRange={3},{4}",
                KeyframeCount, ComponentCount, Duration, ValidRangeStart, ValidRangeEnd);
        }
        #endregion



    }
}
